from pymodbus.client import ModbusTcpClient
import pandas as pd
import os

# 读取 Excel 点表
# 获取当前脚本文件所在的目录
current_dir = os.path.dirname(os.path.realpath(__file__))
# 拼接 Excel 文件的完整路径
file_path = os.path.join(current_dir, '转发数据点表.xlsx')

# 定义 Excel 表格各列的名称
column_names = ['量测点名称', '公共地址', '寄存器地址', '功能码', '大小端', '数值类型', '比例']

try:
    # 读取 Excel 文件，跳过第一行（原表头），不使用原表头，使用自定义的列名
    df = pd.read_excel(file_path, skiprows=1, header=None, names=column_names)
    # 删除包含缺失值的行
    df = df.dropna()
    # 将 '寄存器地址' 列的数据类型转换为整数
    df['寄存器地址'] = df['寄存器地址'].astype(int)
    # 将 '功能码' 列的数据类型转换为整数
    df['功能码'] = df['功能码'].astype(int)
    # 将 '比例' 列的数据转换为数值类型，对于无法转换的值将其置为 NaN
    df['比例'] = pd.to_numeric(df['比例'], errors='coerce')
    print("点表加载成功！")
except Exception as e:
    # 若读取点表过程中出现异常，打印错误信息并退出程序
    print(f"读取点表失败: {e}")
    exit(1)

# 配置 Modbus 连接
# 定义 Modbus 服务器的 IP 地址
MODBUS_SERVER_IP = "127.0.0.1"
# 定义 Modbus 服务器的端口号
MODBUS_SERVER_PORT = 1502
# 定义 Modbus 从站的 ID
SLAVE_ID = 1

# 创建三个 Modbus TCP 客户端实例，用于与 Modbus 服务器进行通信
modbus_client1 = ModbusTcpClient(MODBUS_SERVER_IP, port=MODBUS_SERVER_PORT)
modbus_client2 = ModbusTcpClient(MODBUS_SERVER_IP, port=MODBUS_SERVER_PORT)
modbus_client3 = ModbusTcpClient(MODBUS_SERVER_IP, port=MODBUS_SERVER_PORT)

# 尝试连接第一个 Modbus 客户端
if not modbus_client1.connect():
    print("Modbus1 连接失败！")
    exit(1)
else:
    print("Modbus1 连接成功！")

# 尝试连接第二个 Modbus 客户端
if not modbus_client2.connect():
    print("Modbus2 连接失败！")
    exit(1)
else:
    print("Modbus2 连接成功！")

# 尝试连接第三个 Modbus 客户端
if not modbus_client3.connect():
    print("Modbus3 连接失败！")
    exit(1)
else:
    print("Modbus3 连接成功！")

# 过滤寄存器 1-27（功能码 4）
# 从点表中筛选出寄存器地址在 1 到 27 之间且功能码为 4 的行
filtered_registers = df[(df['寄存器地址'] >= 1) & (df['寄存器地址'] <= 27) & (df['功能码'] == 4)]
# 过滤寄存器 1001-1006（功能码 6）
# 从点表中筛选出寄存器地址在 1001 到 1006 之间且功能码为 6 的行
write_registers = df[(df['寄存器地址'] >= 1001) & (df['寄存器地址'] <= 1006) & (df['功能码'] == 6)]

def get_data(option):
    """
    从 Modbus 设备读取寄存器数据，最后得到的是所有的(共27个)
    ['PV_逆变总发电量', 'PV_逆变当月发电量', 'PV_逆变上月发电量', 'PV_逆变当日发电量', 'PV_逆变昨日发电量',
    'PV_逆变今年发电量', 'PV_逆变去年发电量', '直流电压1', '直流电流1', '直流电压2', '直流电流2', '总直流输入功率',
    'A相电压', 'A相电流', '有功功率', '电网频率', '蓄电池电压', '蓄电池电流', '蓄电池电流方向', '电池容量SOC',
    '电池健康SOH', '电池电压', '电池电流', '电池充电限流', '电池放电限流', '蓄电池功率', '逆变器电网端口总功率']
    :param option: 操作选项，应为 "get_data"
    :return: 包含读取数据的列表，如果读取失败则对应位置为 None
    """
    # 若操作选项为 "get_data"
    if option == "get_data":
        # 用于存储读取到的数据
        response_data = []
        # 遍历过滤后的寄存器信息
        for _, row in filtered_registers.iterrows():
            # 得到的数据格式是每一行的数据，都带着不同的index
            # 获取寄存器地址
            address = row['寄存器地址']
            # 获取比例因子
            scale = row['比例']
            # 获取数据类型
            data_type = row['数值类型']

            # 从 Modbus 设备读取指定地址的输入寄存器数据
            # count=1表示每一次只读一个
            response = modbus_client1.read_input_registers(address=address, count=1, slave=SLAVE_ID)
            # 若读取成功
            if not response.isError():
                # 获取原始值
                raw_value = response.registers[0]

                # 处理有符号整数
                if data_type == 'i16':
                    # 若原始值小于 32768，则直接使用，否则减去 65536 得到有符号值
                    value = raw_value if raw_value < 32768 else raw_value - 65536
                else:
                    value = raw_value

                # 应用比例因子得到缩放后的值
                scaled_value = value / scale
                # 将缩放后的值添加到结果列表中
                response_data.append(scaled_value)
            else:
                # 若读取失败，将 None 添加到结果列表中
                response_data.append(None)

        print(f"读取成功: {response_data}")
        return response_data

def input_data(option, array_data=[1000, 1000, 1000, 222, 300, 300]):
    """
    向 Modbus 设备写入寄存器数据,其中array_data第五个参数为电池强制充放电的标志，为222或者238
    :param option: 操作选项，应为 "input_data"
    :param array_data: 要写入的数据列表，一共有6个的数据，['光伏限制功率', '电池限制功率', '电网限制功率',
    '电池强制充放电', '电池强制充电功率', '电池强制放电功率']所以长度应为 6
    :return: 包含写入结果的字典，状态为 "ok" 或 "partial"，结果列表包含每个寄存器的写入状态
    """
    # 若操作选项为 "input_data"
    if option == "input_data":
        # 检查输入数据列表的长度是否为 6
        if len(array_data) != 6:
            print("错误: input_data 需要一个长度为 6 的数组！")
            return None

        # 用于标记写入操作是否全部成功
        success = True
        # 用于存储每个寄存器的写入结果
        results = []

        # 遍历需要写入的寄存器信息
        for i, (_, row) in enumerate(write_registers.iterrows()):
            # 获取寄存器地址
            address = row['寄存器地址']
            # 获取比例因子
            scale = row['比例']
            # 获取数据类型
            data_type = row['数值类型']
            # 获取要写入的值
            input_value = array_data[i]

            # 应用比例因子并转换为整数
            try:
                # 将输入值转换为浮点数，乘以比例因子，然后四舍五入并转换为整数
                raw_value = int(round(float(input_value) * scale))
            except ValueError:
                # 若输入值无法转换为数字，标记写入操作部分失败
                success = False
                # 记录该寄存器的写入状态为 "invalid value"
                results.append({"address": address, "status": "invalid value"})
                continue

            # 校验 u16 范围
            if raw_value < 0 or raw_value > 65535:
                # 若超出 u16 范围，将值截断到 0 到 65535 之间
                raw_value = max(0, min(raw_value, 65535))

            # 向 Modbus 设备写入指定地址的寄存器数据
            response = modbus_client2.write_register(address=address, value=raw_value, slave=SLAVE_ID)
            if response.isError():
                # 若写入失败，标记写入操作部分失败
                success = False
                # 记录该寄存器的写入状态为 "failed"
                results.append({"address": address, "status": "failed"})
            else:
                # 若写入成功，记录该寄存器的写入状态为 "success"
                results.append({"address": address, "status": "success"})

        return {"status": "ok" if success else "partial", "results": results}
    else:
        print("错误: 未知的 option 参数！")
        return None

def get_or_input_data(option, array_data=[1000, 1000, 1000, 222, 300, 300]):
    """
    直接读取或写入 Modbus 设备，不再使用服务器-客户端模式。

    :param option: "get_data" 读取 1-27 号寄存器 | "input_data" 写入 1001-1006 号寄存器
    :param array_data: 仅在 "input_data" 时使用，默认值为 [1000, 1000, 1000, 222, 300, 300]
    :return: "get_data" 返回长度 27 的数组 | "input_data" 返回写入结果
    """
    # 读取寄存器
    if option == "get_data":
        # 用于存储读取到的数据
        response_data = []
        # 遍历过滤后的寄存器信息
        for _, row in filtered_registers.iterrows():
            # 获取寄存器地址
            address = row['寄存器地址']
            # 获取比例因子
            scale = row['比例']
            # 获取数据类型
            data_type = row['数值类型']

            # 从 Modbus 设备读取指定地址的输入寄存器数据
            response = modbus_client3.read_input_registers(address=address, count=1, slave=SLAVE_ID)
            # 若读取成功
            if not response.isError():
                # 获取原始值
                raw_value = response.registers[0]

                # 处理有符号整数
                if data_type == 'i16':
                    # 若原始值小于 32768，则直接使用，否则减去 65536 得到有符号值
                    value = raw_value if raw_value < 32768 else raw_value - 65536
                else:
                    value = raw_value

                # 应用比例因子得到缩放后的值
                scaled_value = value / scale
                # 将缩放后的值添加到结果列表中
                response_data.append(scaled_value)
            else:
                # 若读取失败，将 None 添加到结果列表中
                response_data.append(None)

        print(f"读取成功: {response_data}")
        return response_data

    # 写入寄存器
    elif option == "input_data":
        # 检查输入数据列表的长度是否为 6
        if len(array_data) != 6:
            print("错误: input_data 需要一个长度为 6 的数组！")
            return None

        # 用于标记写入操作是否全部成功
        success = True
        # 用于存储每个寄存器的写入结果
        results = []

        # 遍历需要写入的寄存器信息
        for i, (_, row) in enumerate(write_registers.iterrows()):
            # 获取寄存器地址
            address = row['寄存器地址']
            # 获取比例因子
            scale = row['比例']
            # 获取数据类型
            data_type = row['数值类型']
            # 获取要写入的值
            input_value = array_data[i]

            # 应用比例因子并转换为整数
            try:
                # 将输入值转换为浮点数，乘以比例因子，然后四舍五入并转换为整数
                raw_value = int(round(float(input_value) * scale))
            except ValueError:
                # 若输入值无法转换为数字，标记写入操作部分失败
                success = False
                # 记录该寄存器的写入状态为 "invalid value"
                results.append({"address": address, "status": "invalid value"})
                continue

            # 校验 u16 范围
            if raw_value < 0 or raw_value > 65535:
                # 若超出 u16 范围，将值截断到 0 到 65535 之间
                raw_value = max(0, min(raw_value, 65535))

            # 向 Modbus 设备写入指定地址的寄存器数据
            response = modbus_client3.write_register(address=address, value=raw_value, slave=SLAVE_ID)
            if response.isError():
                # 若写入失败，标记写入操作部分失败
                success = False
                # 记录该寄存器的写入状态为 "failed"
                results.append({"address": address, "status": "failed"})
            else:
                # 若写入成功，记录该寄存器的写入状态为 "success"
                results.append({"address": address, "status": "success"})

        return {"status": "ok" if success else "partial", "results": results}

    else:
        print("错误: 未知的 option 参数！")
        return None

# 关闭 Modbus 连接
modbus_client1.close()
modbus_client2.close()
modbus_client3.close()
print('modbus1,2,3连接关闭')